Using R in hydrology (EGU2017 short course)

Instructors: Shaun Harrigan, Katie Smith, Berry Boessenkool and Daniel Klotz
Organizer: Berry Boessenkool, PhD student at Potsdam University (Germany)
Contact: Questions and feedback are welcome via berry-b@gmx.de

These slides and all other course materials can be found at
github.com/brry/rhydro
Download the github course repository with all the materials including the datasets and presentation source code.
This is an R Markdown Notebook.
For discussions, please visit the Hydrology in R Facebook group.
Before running the code blocks below, we suggest to get package installation instructions by running:

source("https://raw.githubusercontent.com/brry/rhydro/master/checkpc.R")


Aim and contents of this workshop

We want to:

We can not:

We have prepared:

 

Before we get started, please let us know your current R knowledge level by filling out the short survey at
bit.ly/knowR


top

Report

Good coding practice, report generation (Rstudio, rmarkdown, R notebook)
Daniel Klotz

Introduction

goals

goals

Why I use R

Why I did not use:

equals

equals

Whats great about R:

  library(ggplot2)
  test_data <- mpg
  test_plot <- ggplot(test_data, aes(displ, hwy, colour = class)) + 
    geom_point()
  test_plot

Why I decided to use R:

pipe

pipe

Previously:

  aggregation_function <- function(x) {
    round(mean(x),2)
  }
  mtcars_subset <- subset(mtcars,hp > 100)
  mtcars_aggregated <- aggregate(. ~ cyl, data = mtcars_subset, FUN = aggregation_function)
  car_data1 <- transform(mtcars_aggregated, kpl = mpg*0.4251)
  print(car_data1)

Now:

library(magrittr)
car_data2 <- 
  mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %>%
  print

btw: You can integrate other programming languages with ease. Here an example from Yihui Xie for the use of Fortran in Rmarkdown:

  1. Compile Code:
```r
C Fortran test
      subroutine fexp(n, x)
      double precision x
C  output
      integer n, i
C  input value
      do 10 i=1,n
         x=dexp(dcos(dsin(dble(float(i)))))
  10  continue
      return
      end
```
  1. Run Code:
```r
res = .Fortran("fexp", n=100000L, x=0)
str(res)
```

Be happy with the result: > ## List of 2 > ## $ n: int 100000 > ## $ x: num 2.72


Markdown

HTML: HyperText Markdown Language

John Gruber:

John Gruber

Comparison: Markdown vs. Latex Comparison

Rstudio provides cheat-sheets with the most important informations about many of their “favorite” packages & software:

Cheat Sheet

Rmarkdown

In Rstudio:

Rmark1

Rmark2

Rmark2

“Native” Formats:

Much more possible if you adress pandoc directly: pandoc

Information in the text can be automatically updated with the rest of the document: [time for coffee

Examples

Small Websites

Cayman Theme

Cayman Theme

Books (blogdown)

bookdown1 bookdown2

Blogs (hugo)

hugo

Presentations

bookdown1

Widgets

cran-gauge superzip

Apps (Shiny)

shiny shiny2


top

GIS

Using R as GIS (reading a rainfall shapefile + Kriging, sf + leaflet + mapview + OSMscale)
Berry Boessenkool

Shapefiles

Reading shapefiles with maptools::readShapeSpatial and rgdal::readOGR is obsolete.
Instead, use sf::st_read. sf is on CRAN since oct 2016.
Main reaction when using sf: “Wow, that is fast!”
Download the shapefile or better: download the whole github course repository

rain <- sf::st_read("data/PrecBrandenburg/niederschlag.shp")
Reading layer `niederschlag' from data source `/home/berry/Dropbox/R/rhydro/presentations/data/PrecBrandenburg/niederschlag.shp' using driver `ESRI Shapefile'
converted into: POLYGON
Simple feature collection with 277 features and 1 field
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 3250175 ymin: 5690642 xmax: 3483631 ymax: 5932731
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=0 +lon_0=15 +k=0.9996 +x_0=3500000 +y_0=0 +ellps=GRS80 +units=m +no_defs

Central points of rainfall Thiessen polygons

centroids <- sf::st_centroid(rain)
centroids <- sf::st_coordinates(centroids)
centroids <- as.data.frame(centroids)

top

Plotting, maps

Static plot:

plot(rain[,1])

Static map:

prj <- sf::st_crs(rain)$proj4string
cent_ll <- OSMscale::projectPoints(Y,X, data=centroids, to=OSMscale::pll(), from=prj)
#map_static <- OSMscale::pointsMap(y,x, cent_ll, fx=0.08, type="maptoolkit-topo", zoom=6)
#save(map_static, file="data/map_static.Rdata")
load("data/map_static.Rdata")
OSMscale::pointsMap(y,x, cent_ll, map=map_static)
Done. Now plotting...

Interactive map:

library(leaflet)
cent_ll$info <- paste0(sample(letters,nrow(cent_ll),TRUE), ", ", round(cent_ll$x,2), 
                       ", ", round(cent_ll$y,2))
leaflet(cent_ll) %>% addTiles() %>% addCircleMarkers(lng=~x, lat=~y, popup=~info)

Interactive map of shapefile:

# make sure to have installed the development version of mapview: 
# devtools::install_github("environmentalinformatics-marburg/mapview", ref = "develop")
library(berryFunctions) # classify, seqPal
col <- seqPal(n=100, colors=c("red","yellow","blue"))[classify(rain$P1)$index]
mapview::mapview(rain, col.regions=col)

top

Kriging

Plot original points colored by third dimension:

pcol <- colorRampPalette(c("red","yellow","blue"))(50)
x <- centroids$X # use cent_ll$x for projected data
y <- centroids$Y
berryFunctions::colPoints(x, y, rain$P1, add=FALSE, col=pcol)

Calculate the variogram and fit a semivariance curve

library(geoR)
--------------------------------------------------------------
 Analysis of Geostatistical Data
 For an Introduction to geoR go to http://www.leg.ufpr.br/geoR
 geoR version 1.7-5.2 (built on 2016-05-02) is now loaded
--------------------------------------------------------------
geoprec <- as.geodata(cbind(x,y,rain$P1))
vario <- variog(geoprec, max.dist=130000) # other maxdist for lat-lon data
variog: computing omnidirectional variogram
fit <- variofit(vario)
variofit: covariance model used is matern 
variofit: weights used: npairs 
variofit: minimisation function used: optim 
initial values not provided - running the default search
variofit: searching for best initial value ... selected values:
              sigmasq   phi        tausq kappa
initial.value "1325.81" "19999.05" "0"   "0.5"
status        "est"     "est"      "est" "fix"
loss value: 104819060.429841 
plot(vario)
lines(fit)

Determine a useful resolution (keep in mind that computing time rises exponentially with grid size)

# distance to closest other point:
d <- sapply(1:length(x), function(i)
            min(berryFunctions::distance(x[i], y[i], x[-i], y[-i])) )
# for lat-long data use (2017-Apr only available in github version of OSMscale)
# d <- OSMscale::maxEarthDist(y,x, data=cent_ll, fun=min)
hist(d/1000, breaks=20, main="distance to closest gauge [km]")

mean(d/1000) # 8 km
[1] 8.165713

Perform kriging on a grid with that resolution

res <- 1000 # 1 km, since stations are 8 km apart on average
grid <- expand.grid(seq(min(x),max(x),res),
                    seq(min(y),max(y),res))
krico <- krige.control(type.krige="OK", obj.model=fit)
#krobj <- krige.conv(geoprec, loc=grid, krige=krico)
#save(krobj, file="data/krobj.Rdata")
load("data/krobj.Rdata") # line above is too slow for recreation each time

Plot the interpolated values with image or an equivalent function (see Rclick 4.15) and add contour lines.

par(mar=c(0,3,0,3))
geoR:::image.kriging(krobj, col=pcol)
colPoints(x, y, rain$P1, col=pcol, legargs=list(horiz=F, title="Prec",y1=0.1,x1=0.9))
points(x,y)
plot(rain, col=NA, add=TRUE)


top

Discharge

River discharge time-series visualisation and extreme value statistics (animation + extremeStat)
Berry Boessenkool

Read, plot and aggregate data

Datasets from http://nrfa.ceh.ac.uk/data/station/meanflow/39072
Download discharge1, discharge2_xxx, discharge3_xxx, or better: download the whole github course repository

Read and transform data

Q <- read.table("data/discharge39072.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q) <- c("date","Royal_Windsor_Park")
Q$date <- as.Date(Q$date, format="%Y-%m-%d")

Examine data

head(Q)
str(Q)
'data.frame':   13222 obs. of  2 variables:
 $ date              : Date, format: "1979-07-20" "1979-07-21" "1979-07-22" ...
 $ Royal_Windsor_Park: num  33.4 32.5 33.1 30.6 30.1 ...

Simple time series plot

plot(Q, type="l", col="blue")

Publication-ready graphics

png("DischargeVis.png", width=20, height=10, units="cm", res=500)
#pdf("DischargeVis.pdf", width=20/2.5, height=10/2.5) # vector graph
par(mar=c(3.5,3.5,2.5,0.2), mgp=c(2.3,0.7,0), las=1)
plot(Q, type="l", col="blue", main="NRFA: Thames\nRoyal Windsor Park",
     xlab="Date", ylab="Discharge  [m\U{00B3}/s]")
dev.off()
null device 
          1 

Annual maxima, German hydrological year split at Oct 31

Q$hydyear <- as.numeric(format(Q$date+61, "%Y"))
annmax <- tapply(Q$Royal_Windsor_Park, Q$hydyear, max, na.rm=TRUE)
annmax <- annmax[-1]
hydyear <- as.numeric(names(annmax)) 
plot(hydyear, annmax, type="o", las=1)

Extreme value statistics

library(extremeStat)
# Loaded extremeStat 1.3.1 (2017-02-16). Package restructured since 0.6.0 (2016-12-13).
# Computing functions don't plot anymore and some are renamed. See help('extremeStat-deprecated')
dlf <- distLfit(annmax)
Note in distLfit: dat was not a vector.
distLfit execution took 0.22 seconds.
plotLfit(dlf)

plotLfit(dlf, cdf=TRUE)

dle <- distLextreme(dlf=dlf, RPs=c(5,10,50,100), gpd=FALSE)
plotLextreme(dle)

Logarithmic plot with many fitted distribution functions

plotLextreme(dle, nbest=16, log=TRUE)

Return values (discharge estimates for given return periods)

dlf$returnlev
NULL

In reality, please use non-stationary EVS!

Uncertainty band for Wakeby distribution fit estimate

dle_boot <- distLexBoot(dle, n=10, nbest=1)

   |+++++                                             | 10% ~01s          
   |++++++++++                                        | 20% ~00s          
   |+++++++++++++++                                   | 30% ~00s          
   |++++++++++++++++++++                              | 40% ~00s          
   |+++++++++++++++++++++++++                         | 50% ~00s          
   |++++++++++++++++++++++++++++++                    | 60% ~00s          
   |+++++++++++++++++++++++++++++++++++               | 70% ~00s          
   |++++++++++++++++++++++++++++++++++++++++         | 80% ~00s          
   |+++++++++++++++++++++++++++++++++++++++++++++    | 90% ~00s          
   |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed = 01s
plotLexBoot(dle_boot, distcol="green")

More details in the package vignette

vignette("extremeStat")

Animated movie

Read data from several discharge stations

if(FALSE){
Q2 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q2) <- c("date","dummy1");  Q2$date <- as.Date(Q2$date, format="%Y-%m-%d")
Q3 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q3) <- c("date","dummy2");  Q3$date <- as.Date(Q3$date, format="%Y-%m-%d")
}
Q2 <- Q ; Q2[,2] <- Q2[,2] + 100 ; Q2 <- Q2[-(1:100),]
Q3 <- Q ; Q3[,2] <- Q3[,2] -  50 ; Q3 <- head(Q3, -365*3)
colnames(Q2)[2] <- "dummy1"; colnames(Q3)[2] <- "dummy2"

Merge datasets

dis <- Reduce(function(...) merge(..., all=T), list(Q,Q2,Q3))

Code to generate one movie slice

library(berryFunctions) # for lim0, monthAxis, textField, etc
scene <- function(i, vlcnote=TRUE, cex=1.2)
{
 sel <- 0:120
 dis2 <- dis[i + sel, ]
 stat <- c("Royal_Windsor_Park", "dummy1", "dummy2")
 col <- c("red", "blue", "orange")
 names(col) <- stat
 plot(dis2$date,dis2$Royal_Windsor_Park, type="n", ylim=lim0(500),  las=1, 
      xaxt="n", yaxt="n", cex.lab=cex, xlab="", 
      ylab="Discharge  [m\U{00B3}/s]", xaxs="i")
 axis(2, cex.axis=cex, las=1)
 Sys.setlocale("LC_TIME", "C")
 monthAxis(midmonth=TRUE, format="%b\n%Y", cex=cex, mgp=c(3,3.5,0))
 abline(h=1:6*100, v=dis2$date[format(dis2$date,"%d")=="01"], col=8)
 for(s in stat) lines(dis2$date, dis2[,s], lwd=4, col=col[s])
 xi <- seqR(sel,len=length(stat)+2)[-1]
 xi <- head(xi, -1)
 textField(dis2$date[xi], diag(as.matrix(dis2[xi,stat])), stat, cex=cex, col=col)
 box()
 if(vlcnote) mtext("VLC: 'e': single frame forward\n'SHIFT+LEFT': few seconds back",
                   side=3, line=-9, outer=TRUE, adj=0.95, cex=cex)
}
par(mar=c(5,5,0.5,0.5), mgp=c(3,0.7,0))
scene(150)

Actual movie

library(animation)
saveVideo({par(mar=c(6,8,1,1), mgp=c(5.5,0.7,0))
 dummy <- pbsapply(seq(0, by=3, len=50), scene, cex=2); rm(dummy)
}, video.name="Qmovie.mp4", interval=0.1, ffmpeg="/usr/bin/ffmpeg", 
ani.width=7*200, ani.height=5*200)

Open video in default mp4player


top

Hydmod

Hydrological modelling with airGR
Katie Smith


top

EDA

Exploratory Data Analysis including flow duration curve and trend analysis on time-series
Shaun Harrigan


top

Discussion

Please give us feedback at bit.ly/feedbackR

For discussions, please use the Hydrology in R Facebook group.

LS0tCnRpdGxlOiAiUmh5ZHJvIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgdG9jOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmlmIChrbml0cjo6OmlzX2h0bWxfb3V0cHV0KCkpIHsKICBrbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFKQp9CmBgYAoKPGZvbnQgc2l6ZT0iNiI+VXNpbmcgUiBpbiBoeWRyb2xvZ3kgKEVHVTIwMTcgc2hvcnQgY291cnNlKTwvZm9udD4gICAKCipJbnN0cnVjdG9ycyo6IFNoYXVuIEhhcnJpZ2FuLCBLYXRpZSBTbWl0aCwgQmVycnkgQm9lc3Nlbmtvb2wgYW5kIERhbmllbCBLbG90eiAgCipPcmdhbml6ZXIqOiBCZXJyeSBCb2Vzc2Vua29vbCwgUGhEIHN0dWRlbnQgYXQgUG90c2RhbSBVbml2ZXJzaXR5IChHZXJtYW55KSAgCipDb250YWN0KjogUXVlc3Rpb25zIGFuZCBmZWVkYmFjayBhcmUgd2VsY29tZSB2aWEgPGJlcnJ5LWJAZ214LmRlPgoKVGhlc2Ugc2xpZGVzIGFuZCBhbGwgb3RoZXIgY291cnNlIG1hdGVyaWFscyBjYW4gYmUgZm91bmQgYXQgIAo8Zm9udCBzaXplPSI2Ij5bZ2l0aHViLmNvbS9icnJ5L3JoeWRyb10oaHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvKTwvZm9udD4gICAKW0Rvd25sb2FkIHRoZSBnaXRodWIgY291cnNlIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby9hcmNoaXZlL21hc3Rlci56aXApCndpdGggYWxsIHRoZSBtYXRlcmlhbHMgaW5jbHVkaW5nIHRoZSBkYXRhc2V0cyBhbmQgcHJlc2VudGF0aW9uIHNvdXJjZSBjb2RlLiAgClRoaXMgaXMgYW4gW1IgTWFya2Rvd24gTm90ZWJvb2tdKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcl9ub3RlYm9va3MuaHRtbCkuICAKRm9yIGRpc2N1c3Npb25zLCBwbGVhc2UgdmlzaXQgdGhlIApbSHlkcm9sb2d5IGluIFIgRmFjZWJvb2sgZ3JvdXBdKGh0dHBzOi8vd3d3LmZhY2Vib29rLmNvbS9ncm91cHMvMTEzMDIxNDc3NzEyMzkwOS8pLiAgCkJlZm9yZSBydW5uaW5nIHRoZSBjb2RlIGJsb2NrcyBiZWxvdywgd2Ugc3VnZ2VzdCB0byBnZXQgcGFja2FnZSBpbnN0YWxsYXRpb24gaW5zdHJ1Y3Rpb25zIGJ5IHJ1bm5pbmc6CmBgYFIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYnJyeS9yaHlkcm8vbWFzdGVyL2NoZWNrcGMuUiIpCmBgYAoKXAoKKipBaW0gYW5kIGNvbnRlbnRzIG9mIHRoaXMgd29ya3Nob3AqKgoKV2Ugd2FudCB0bzogIAoKKiBTaG93IG9mZiBob3cgYXdlc29tZSBSIGlzIGZvciBoeWRyb2xvZ3kgKGl0J3MgUi1zb21lIV5eKSAgCiogQ29udmluY2UgeW91IHRvIHN0YXJ0IG9yIGNvbnRpbnVlIHVzaW5nIFIgIAoqIFByb3ZpZGUgYWxsIHRoZSBjb2RlIGZvciB5b3UgYXMgYSBzdGFydGluZyBwb2ludAoKV2UgY2FuIG5vdDogIAoKKiBUZWFjaCB5b3UgYWN0dWFsIFIgY29kaW5nICg5MCBtaW5zIGlzIHRvbyBzaG9ydCBmb3IgYSB0dXRvcmlhbCkKCldlIGhhdmUgcHJlcGFyZWQ6CgoqIFtHb29kIGNvZGluZyBwcmFjdGljZSwgcmVwb3J0IGdlbmVyYXRpb25dKCNyZXBvcnQpIChSc3R1ZGlvLCBgcm1hcmtkb3duYCwgUiBub3RlYm9vaykKKiBbVXNpbmcgUiBhcyBHSVNdKCNnaXMpIChyZWFkaW5nIGEgcmFpbmZhbGwgc2hhcGVmaWxlICsgS3JpZ2luZywgYHNmYCArIGBsZWFmbGV0YCArIGBtYXB2aWV3YCArIGBPU01zY2FsZWApCiogW1JpdmVyIGRpc2NoYXJnZSB0aW1lLXNlcmllc10oI2Rpc2NoYXJnZSkgdmlzdWFsaXNhdGlvbiBhbmQgZXh0cmVtZSB2YWx1ZSBzdGF0aXN0aWNzIChgYW5pbWF0aW9uYCArIGBleHRyZW1lU3RhdGApCiogW0h5ZHJvbG9naWNhbCBtb2RlbGxpbmddKCNoeWRtb2QpIHdpdGggYGFpckdSYAoqIFtFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzXSgjZWRhKSBpbmNsdWRpbmcgZmxvdyBkdXJhdGlvbiBjdXJ2ZSBhbmQgdHJlbmQgYW5hbHlzaXMgb24gdGltZS1zZXJpZXMKClwgCgpCZWZvcmUgd2UgZ2V0IHN0YXJ0ZWQsIHBsZWFzZSBsZXQgdXMga25vdyB5b3VyIGN1cnJlbnQgUiBrbm93bGVkZ2UgbGV2ZWwgYnkgZmlsbGluZyBvdXQgdGhlIHNob3J0IHN1cnZleSBhdCAgCjxmb250IHNpemU9IjYiPltiaXQubHkva25vd1JdKGh0dHBzOi8vYml0Lmx5L2tub3dSKTwvZm9udD4gCgpcCgpbdG9wXSgjdG9wKQoKIyBSZXBvcnQKR29vZCBjb2RpbmcgcHJhY3RpY2UsIHJlcG9ydCBnZW5lcmF0aW9uIChSc3R1ZGlvLCBgcm1hcmtkb3duYCwgUiBub3RlYm9vaykgIAoqKkRhbmllbCBLbG90eioqCgojIyBJbnRyb2R1Y3Rpb24KCiFbZ29hbHNdKGRhbmllbC9pbnRyby9nb2Fscy5qcGVnKQoKIyMjIFdoeSBJIHVzZSBSCgpXaHkgSSBkaWQgbm90IHVzZTogCgohW2VxdWFsc10oZGFuaWVsL2ludHJvL2VxdWFscy5qcGVnKQoKCldoYXRzIGdyZWF0IGFib3V0IFI6IApgYGB7cn0KICBsaWJyYXJ5KGdncGxvdDIpCiAgdGVzdF9kYXRhIDwtIG1wZwogIHRlc3RfcGxvdCA8LSBnZ3Bsb3QodGVzdF9kYXRhLCBhZXMoZGlzcGwsIGh3eSwgY29sb3VyID0gY2xhc3MpKSArIAogICAgZ2VvbV9wb2ludCgpCiAgdGVzdF9wbG90CmBgYAoKV2h5IEkgZGVjaWRlZCB0byB1c2UgUjogCgohW3BpcGVdKGRhbmllbC9pbnRyby9tYWdyaXR0ci5qcGVnKQoKUHJldmlvdXNseToKYGBge3J9CiAgYWdncmVnYXRpb25fZnVuY3Rpb24gPC0gZnVuY3Rpb24oeCkgewogICAgcm91bmQobWVhbih4KSwyKQogIH0KICBtdGNhcnNfc3Vic2V0IDwtIHN1YnNldChtdGNhcnMsaHAgPiAxMDApCiAgbXRjYXJzX2FnZ3JlZ2F0ZWQgPC0gYWdncmVnYXRlKC4gfiBjeWwsIGRhdGEgPSBtdGNhcnNfc3Vic2V0LCBGVU4gPSBhZ2dyZWdhdGlvbl9mdW5jdGlvbikKICBjYXJfZGF0YTEgPC0gdHJhbnNmb3JtKG10Y2Fyc19hZ2dyZWdhdGVkLCBrcGwgPSBtcGcqMC40MjUxKQogIHByaW50KGNhcl9kYXRhMSkKYGBgCgpOb3c6CmBgYHtyfQpsaWJyYXJ5KG1hZ3JpdHRyKQpjYXJfZGF0YTIgPC0gCiAgbXRjYXJzICU+JQogIHN1YnNldChocCA+IDEwMCkgJT4lCiAgYWdncmVnYXRlKC4gfiBjeWwsIGRhdGEgPSAuLCBGVU4gPSAuICU+JSBtZWFuICU+JSByb3VuZCgyKSkgJT4lCiAgdHJhbnNmb3JtKGtwbCA9IG1wZyAlPiUgbXVsdGlwbHlfYnkoMC40MjUxKSkgJT4lCiAgcHJpbnQKYGBgCgoKKipidHc6KioKWW91IGNhbiBpbnRlZ3JhdGUgb3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIHdpdGggZWFzZS4gSGVyZSBhbiBleGFtcGxlIGZyb20gCltZaWh1aSBYaWVdKGh0dHBzOi8veWlodWkubmFtZS9rbml0ci9kZW1vL2VuZ2luZXMvKSBmb3IgdGhlIHVzZSBvZiBgRm9ydHJhbmAgCmluIFJtYXJrZG93bjoKCjEuIENvbXBpbGUgQ29kZToKICAgIGBgYHtyIGNvbXBmb3J0LCBlbmdpbmU9J2ZvcnRyYW4nLCByZXN1bHRzPSdoaWRlJywgZXZhbD1GQUxTRX0KICAgIEMgRm9ydHJhbiB0ZXN0CiAgICAgICAgICBzdWJyb3V0aW5lIGZleHAobiwgeCkKICAgICAgICAgIGRvdWJsZSBwcmVjaXNpb24geAogICAgQyAgb3V0cHV0CiAgICAgICAgICBpbnRlZ2VyIG4sIGkKICAgIEMgIGlucHV0IHZhbHVlCiAgICAgICAgICBkbyAxMCBpPTEsbgogICAgICAgICAgICAgeD1kZXhwKGRjb3MoZHNpbihkYmxlKGZsb2F0KGkpKSkpKQogICAgICAxMCAgY29udGludWUKICAgICAgICAgIHJldHVybgogICAgICAgICAgZW5kCiAgICBgYGAKCjIuIFJ1biBDb2RlOgogICAgYGBge3IgdGVzdGZvcnQsIGNvbGxhcHNlPVRSVUUsIGV2YWwgPSBGQUxTRX0KICAgIHJlcyA9IC5Gb3J0cmFuKCJmZXhwIiwgbj0xMDAwMDBMLCB4PTApCiAgICBzdHIocmVzKQogICAgYGBgCgpCZSBoYXBweSB3aXRoIHRoZSByZXN1bHQ6IAo+ICAgICBgIyMgTGlzdCBvZiAyYAo+ICAgICBgIyMgICQgbjogaW50IDEwMDAwMGAKPiAgICAgYCMjICAkIHg6IG51bSAyLjcyYAoKLS0tCgojIyBNYXJrZG93biAKSFQqKk0qKkw6IEh5cGVyVGV4dCAqKk1hcmtkb3duKiogTGFuZ3VhZ2UKCkpvaG4gR3J1YmVyOgoKWyFbSm9obiBHcnViZXJdKGRhbmllbC9pbnRyby9Kb2huX0dydWJlcl93aWtpLmpwZWcpXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Kb2huX0dydWJlcikKCgpDb21wYXJpc29uOiBNYXJrZG93biB2cy4gTGF0ZXggClshW0NvbXBhcmlzb25dKGRhbmllbC9pbnRyby95aWh1aV9sYXRleC12cy1tYXJrZG93bi5wbmcpXShodHRwczovL3lvdXR1LmJlLzJ5dlcwT183eE9nKQoKClJzdHVkaW8gcHJvdmlkZXMgY2hlYXQtc2hlZXRzIHdpdGggdGhlIG1vc3QgaW1wb3J0YW50IGluZm9ybWF0aW9ucyBhYm91dCBtYW55IApvZiB0aGVpciAiZmF2b3JpdGUiIHBhY2thZ2VzICYgc29mdHdhcmU6CgpbIVtDaGVhdCBTaGVldF0oZGFuaWVsL2ludHJvL1JtYXJrZG93bl9jaGVhdHNoZWV0LnBuZyldKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pCgojIFJtYXJrZG93bgpJbiBSc3R1ZGlvOiAKCjxkaXYgYWxpZ249ImNlbnRlciI+CiAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL3JlcG9ydHMvUm1hcmsxLnBuZyIgYWx0PSJSbWFyazEiIC8+CjwvZGl2PgoKPGRpdiBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHdpZHRoPSI2MDBweCIgc3JjPSJkYW5pZWwvcmVwb3J0cy9SbWFyazIucG5nIiBhbHQ9IlJtYXJrMiIgLz4KPC9kaXY+Cgo8ZGl2IGFsaWduPSJjZW50ZXIiPgogIDxpbWcgd2lkdGg9IjYwMHB4IiBzcmM9ImRhbmllbC9yZXBvcnRzL1JtYXJrMy5wbmciIGFsdD0iUm1hcmsyIiAvPgo8L2Rpdj4KCiJOYXRpdmUiIEZvcm1hdHM6CgotIFtodG1sXShkYW5pZWwvc2hvdy9SbWFyay5odG1sKSAKLSBbcGRmXShkYW5pZWwvc2hvdy9SbWFyay5wZGYpIAotIFt3b3JkXShkYW5pZWwvc2hvdy9SbWFyay5kb2NzKQoKTXVjaCBtb3JlIHBvc3NpYmxlIGlmIHlvdSBhZHJlc3MgcGFuZG9jIGRpcmVjdGx5OiAKWyFbcGFuZG9jXShkYW5pZWwvcmVwb3J0cy9wYW5kb2NfZGlhZ3JhbS5qcGcpXShodHRwOi8vcGFuZG9jLm9yZy8pCgpJbmZvcm1hdGlvbiBpbiB0aGUgdGV4dCBjYW4gYmUgYXV0b21hdGljYWxseSB1cGRhdGVkIHdpdGggdGhlIHJlc3Qgb2YgdGhlIApkb2N1bWVudDoKWyFbdGltZSBmb3IgY29mZmVlXShkYW5pZWwvcmVwb3J0cy9jb2ZmZWUucG5nKQoKIyMjIEV4YW1wbGVzCgojIyMjIFNtYWxsIFdlYnNpdGVzCjxkaXYgYWxpZ249ImNlbnRlciI+CiAgPGEgaHJlZiA9ICJodHRwOi8vcnN0dWRpby5naXRodWIuaW8vdHVmdGUvIj4KICAgIDxpbWcgd2lkdGg9IjYwMHB4IiBzcmM9ImRhbmllbC9leGFtcGxlcy9zd190dWZ0ZS5wbmciIGFsdD0iQ2F5bWFuIFRoZW1lIiAvPgogIDwvYT4KPC9kaXY+Cgo8ZGl2IGFsaWduPSJjZW50ZXIiPgogIDxhIGhyZWYgPSAiaHR0cDovL3lpeHVhbi5jb3MubmFtZS9wcmV0dHlkb2MvY2F5bWFuLmh0bWwiPgogICAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL2V4YW1wbGVzL3N3X2NheW1hbi5wbmciIGFsdD0iQ2F5bWFuIFRoZW1lIiAvPgogIDwvYT4KPC9kaXY+CgojIyMjIEJvb2tzIChibG9nZG93bikKWyFbYm9va2Rvd24xXShkYW5pZWwvZXhhbXBsZXMvYm9va2Rvd25fMS5wbmcpXShodHRwczovL2Jvb2tkb3duLm9yZy8pClshW2Jvb2tkb3duMl0oZGFuaWVsL2V4YW1wbGVzL2Jvb2tkb3duXzIucG5nKV0oaHR0cHM6Ly9ib29rZG93bi5vcmcvKQoKIyMjIEJsb2dzIChodWdvKQpbIVtodWdvXShkYW5pZWwvZXhhbXBsZXMvYmxvZ3NfaHVnbzEucG5nKV0oaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYmxvZ2Rvd24vKQoKIyMjIyBQcmVzZW50YXRpb25zClshW2Jvb2tkb3duMV0oZGFuaWVsL2V4YW1wbGVzL3ByZXNlbnRhdGlvbnNfMS5wbmcpXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tL3JldmVhbGpzX3ByZXNlbnRhdGlvbl9mb3JtYXQuaHRtbCkKCiMjIyMgV2lkZ2V0cyAKWyFbY3Jhbi1nYXVnZV0oZGFuaWVsL2V4YW1wbGVzL3dpZGdldHNfMS5wbmcpXShodHRwczovL2dhbGxlcnkuc2hpbnlhcHBzLmlvL2NyYW4tZ2F1Z2UvKQpbIVtzdXBlcnppcF0oZGFuaWVsL2V4YW1wbGVzL3dpZGdldHNfMi5wbmcpXShodHRwczovL3NoaW55LnJzdHVkaW8uY29tL2dhbGxlcnkvc3VwZXJ6aXAtZXhhbXBsZS5odG1sKQoKIyMjIyBBcHBzIChTaGlueSkgClshW3NoaW55XShkYW5pZWwvZXhhbXBsZXMvc2hpbnlfMS5qcGVnKV0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS9hdXRob3Jpbmdfc2hpbnkuaHRtbCkKWyFbc2hpbnkyXShkYW5pZWwvZXhhbXBsZXMvc2hpbnlfMl9nYWxsZXJ5LnBuZyldKGh0dHBzOi8vc2hpbnkucnN0dWRpby5jb20vZ2FsbGVyeS9zdXBlcnppcC1leGFtcGxlLmh0bWwpCgoKXApbdG9wXSgjdG9wKQoKIyBHSVMKVXNpbmcgUiBhcyBHSVMgKHJlYWRpbmcgYSByYWluZmFsbCBzaGFwZWZpbGUgKyBLcmlnaW5nLCBgc2ZgICsgYGxlYWZsZXRgICsgYG1hcHZpZXdgICsgYE9TTXNjYWxlYCkgIAoqKkJlcnJ5IEJvZXNzZW5rb29sKioKCiMjIyBTaGFwZWZpbGVzCgpSZWFkaW5nIHNoYXBlZmlsZXMgd2l0aCBgbWFwdG9vbHM6OnJlYWRTaGFwZVNwYXRpYWxgIGFuZCBgcmdkYWw6OnJlYWRPR1JgIGlzIG9ic29sZXRlLiAgCkluc3RlYWQsIHVzZSBgc2Y6OnN0X3JlYWRgLiBgc2ZgIGlzIG9uIENSQU4gc2luY2Ugb2N0IDIwMTYuICAKTWFpbiByZWFjdGlvbiB3aGVuIHVzaW5nIHNmOiAiV293LCB0aGF0IGlzIGZhc3QhIiAgCltEb3dubG9hZCB0aGUgc2hhcGVmaWxlXShodHRwczovL21pbmhhc2thbWFsLmdpdGh1Yi5pby9Eb3duR2l0LyMvaG9tZT91cmw9aHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvL3RyZWUvbWFzdGVyL3ByZXNlbnRhdGlvbnMvZGF0YS9QcmVjQnJhbmRlbmJ1cmcpIApvciBiZXR0ZXI6IFtkb3dubG9hZCB0aGUgd2hvbGUgZ2l0aHViIGNvdXJzZSByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vYXJjaGl2ZS9tYXN0ZXIuemlwKQoKYGBge3J9CnJhaW4gPC0gc2Y6OnN0X3JlYWQoImRhdGEvUHJlY0JyYW5kZW5idXJnL25pZWRlcnNjaGxhZy5zaHAiKQpgYGAKCkNlbnRyYWwgcG9pbnRzIG9mIHJhaW5mYWxsIFRoaWVzc2VuIHBvbHlnb25zCmBgYHtyfQpjZW50cm9pZHMgPC0gc2Y6OnN0X2NlbnRyb2lkKHJhaW4pCmNlbnRyb2lkcyA8LSBzZjo6c3RfY29vcmRpbmF0ZXMoY2VudHJvaWRzKQpjZW50cm9pZHMgPC0gYXMuZGF0YS5mcmFtZShjZW50cm9pZHMpCmBgYAoKW3RvcF0oI3RvcCkKCiMjIyBQbG90dGluZywgbWFwcwoKU3RhdGljIHBsb3Q6CmBgYHtyfQpwbG90KHJhaW5bLDFdKQpgYGAKClN0YXRpYyBtYXA6CmBgYHtyfQpwcmogPC0gc2Y6OnN0X2NycyhyYWluKSRwcm9qNHN0cmluZwpjZW50X2xsIDwtIE9TTXNjYWxlOjpwcm9qZWN0UG9pbnRzKFksWCwgZGF0YT1jZW50cm9pZHMsIHRvPU9TTXNjYWxlOjpwbGwoKSwgZnJvbT1wcmopCiNtYXBfc3RhdGljIDwtIE9TTXNjYWxlOjpwb2ludHNNYXAoeSx4LCBjZW50X2xsLCBmeD0wLjA4LCB0eXBlPSJtYXB0b29sa2l0LXRvcG8iLCB6b29tPTYpCiNzYXZlKG1hcF9zdGF0aWMsIGZpbGU9ImRhdGEvbWFwX3N0YXRpYy5SZGF0YSIpCmxvYWQoImRhdGEvbWFwX3N0YXRpYy5SZGF0YSIpCk9TTXNjYWxlOjpwb2ludHNNYXAoeSx4LCBjZW50X2xsLCBtYXA9bWFwX3N0YXRpYykKYGBgCgpJbnRlcmFjdGl2ZSBtYXA6CmBgYHtyfQpsaWJyYXJ5KGxlYWZsZXQpCmNlbnRfbGwkaW5mbyA8LSBwYXN0ZTAoc2FtcGxlKGxldHRlcnMsbnJvdyhjZW50X2xsKSxUUlVFKSwgIiwgIiwgcm91bmQoY2VudF9sbCR4LDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAiLCAiLCByb3VuZChjZW50X2xsJHksMikpCmxlYWZsZXQoY2VudF9sbCkgJT4lIGFkZFRpbGVzKCkgJT4lIGFkZENpcmNsZU1hcmtlcnMobG5nPX54LCBsYXQ9fnksIHBvcHVwPX5pbmZvKQpgYGAKCkludGVyYWN0aXZlIG1hcCBvZiBzaGFwZWZpbGU6CmBgYHtyfQojIG1ha2Ugc3VyZSB0byBoYXZlIGluc3RhbGxlZCB0aGUgZGV2ZWxvcG1lbnQgdmVyc2lvbiBvZiBtYXB2aWV3OiAKIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImVudmlyb25tZW50YWxpbmZvcm1hdGljcy1tYXJidXJnL21hcHZpZXciLCByZWYgPSAiZGV2ZWxvcCIpCmxpYnJhcnkoYmVycnlGdW5jdGlvbnMpICMgY2xhc3NpZnksIHNlcVBhbApjb2wgPC0gc2VxUGFsKG49MTAwLCBjb2xvcnM9YygicmVkIiwieWVsbG93IiwiYmx1ZSIpKVtjbGFzc2lmeShyYWluJFAxKSRpbmRleF0KbWFwdmlldzo6bWFwdmlldyhyYWluLCBjb2wucmVnaW9ucz1jb2wpCmBgYAoKW3RvcF0oI3RvcCkKCiMjIyBLcmlnaW5nCgpQbG90IG9yaWdpbmFsIHBvaW50cyBjb2xvcmVkIGJ5IHRoaXJkIGRpbWVuc2lvbjoKYGBge3J9CnBjb2wgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJyZWQiLCJ5ZWxsb3ciLCJibHVlIikpKDUwKQp4IDwtIGNlbnRyb2lkcyRYICMgdXNlIGNlbnRfbGwkeCBmb3IgcHJvamVjdGVkIGRhdGEKeSA8LSBjZW50cm9pZHMkWQpiZXJyeUZ1bmN0aW9uczo6Y29sUG9pbnRzKHgsIHksIHJhaW4kUDEsIGFkZD1GQUxTRSwgY29sPXBjb2wpCmBgYAoKQ2FsY3VsYXRlIHRoZSB2YXJpb2dyYW0gYW5kIGZpdCBhIHNlbWl2YXJpYW5jZSBjdXJ2ZQpgYGB7cn0KbGlicmFyeShnZW9SKQpnZW9wcmVjIDwtIGFzLmdlb2RhdGEoY2JpbmQoeCx5LHJhaW4kUDEpKQp2YXJpbyA8LSB2YXJpb2coZ2VvcHJlYywgbWF4LmRpc3Q9MTMwMDAwKSAjIG90aGVyIG1heGRpc3QgZm9yIGxhdC1sb24gZGF0YQpmaXQgPC0gdmFyaW9maXQodmFyaW8pCnBsb3QodmFyaW8pCmxpbmVzKGZpdCkKYGBgCgpEZXRlcm1pbmUgYSB1c2VmdWwgcmVzb2x1dGlvbiAKKGtlZXAgaW4gbWluZCB0aGF0IGNvbXB1dGluZyB0aW1lIHJpc2VzIGV4cG9uZW50aWFsbHkgd2l0aCBncmlkIHNpemUpCmBgYHtyfQojIGRpc3RhbmNlIHRvIGNsb3Nlc3Qgb3RoZXIgcG9pbnQ6CmQgPC0gc2FwcGx5KDE6bGVuZ3RoKHgpLCBmdW5jdGlvbihpKQogICAgICAgICAgICBtaW4oYmVycnlGdW5jdGlvbnM6OmRpc3RhbmNlKHhbaV0sIHlbaV0sIHhbLWldLCB5Wy1pXSkpICkKIyBmb3IgbGF0LWxvbmcgZGF0YSB1c2UgKDIwMTctQXByIG9ubHkgYXZhaWxhYmxlIGluIGdpdGh1YiB2ZXJzaW9uIG9mIE9TTXNjYWxlKQojIGQgPC0gT1NNc2NhbGU6Om1heEVhcnRoRGlzdCh5LHgsIGRhdGE9Y2VudF9sbCwgZnVuPW1pbikKaGlzdChkLzEwMDAsIGJyZWFrcz0yMCwgbWFpbj0iZGlzdGFuY2UgdG8gY2xvc2VzdCBnYXVnZSBba21dIikKbWVhbihkLzEwMDApICMgOCBrbQpgYGAKClBlcmZvcm0ga3JpZ2luZyBvbiBhIGdyaWQgd2l0aCB0aGF0IHJlc29sdXRpb24gCmBgYHtyfQpyZXMgPC0gMTAwMCAjIDEga20sIHNpbmNlIHN0YXRpb25zIGFyZSA4IGttIGFwYXJ0IG9uIGF2ZXJhZ2UKZ3JpZCA8LSBleHBhbmQuZ3JpZChzZXEobWluKHgpLG1heCh4KSxyZXMpLAogICAgICAgICAgICAgICAgICAgIHNlcShtaW4oeSksbWF4KHkpLHJlcykpCmtyaWNvIDwtIGtyaWdlLmNvbnRyb2wodHlwZS5rcmlnZT0iT0siLCBvYmoubW9kZWw9Zml0KQoja3JvYmogPC0ga3JpZ2UuY29udihnZW9wcmVjLCBsb2M9Z3JpZCwga3JpZ2U9a3JpY28pCiNzYXZlKGtyb2JqLCBmaWxlPSJkYXRhL2tyb2JqLlJkYXRhIikKbG9hZCgiZGF0YS9rcm9iai5SZGF0YSIpICMgbGluZSBhYm92ZSBpcyB0b28gc2xvdyBmb3IgcmVjcmVhdGlvbiBlYWNoIHRpbWUKYGBgCgpQbG90IHRoZSBpbnRlcnBvbGF0ZWQgdmFsdWVzIHdpdGggYGltYWdlYCBvciBhbiBlcXVpdmFsZW50IGZ1bmN0aW9uIAooc2VlIFtSY2xpY2tdKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JjbGljaykgNC4xNSkgYW5kIGFkZCBjb250b3VyIGxpbmVzLgpgYGB7cn0KcGFyKG1hcj1jKDAsMywwLDMpKQpnZW9SOjo6aW1hZ2Uua3JpZ2luZyhrcm9iaiwgY29sPXBjb2wpCmNvbFBvaW50cyh4LCB5LCByYWluJFAxLCBjb2w9cGNvbCwgbGVnYXJncz1saXN0KGhvcml6PUYsIHRpdGxlPSJQcmVjIix5MT0wLjEseDE9MC45KSkKcG9pbnRzKHgseSkKcGxvdChyYWluLCBjb2w9TkEsIGFkZD1UUlVFKQpgYGAKXApbdG9wXSgjdG9wKQoKIyBEaXNjaGFyZ2UKUml2ZXIgZGlzY2hhcmdlIHRpbWUtc2VyaWVzIHZpc3VhbGlzYXRpb24gYW5kIGV4dHJlbWUgdmFsdWUgc3RhdGlzdGljcyAoYGFuaW1hdGlvbmAgKyBgZXh0cmVtZVN0YXRgKSAgCioqQmVycnkgQm9lc3Nlbmtvb2wqKgoKIyMjIFJlYWQsIHBsb3QgYW5kIGFnZ3JlZ2F0ZSBkYXRhCgpEYXRhc2V0cyBmcm9tIDxodHRwOi8vbnJmYS5jZWguYWMudWsvZGF0YS9zdGF0aW9uL21lYW5mbG93LzM5MDcyPiAgCkRvd25sb2FkIApbZGlzY2hhcmdlMV0oaHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvL3RyZWUvbWFzdGVyL3ByZXNlbnRhdGlvbnMvZGF0YS9kaXNjaGFyZ2UzOTA3Mi5jc3YpLApbZGlzY2hhcmdlMl94eHhdKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby90cmVlL21hc3Rlci9wcmVzZW50YXRpb25zL2RhdGEvZGlzY2hhcmdlLmNzdiksIApbZGlzY2hhcmdlM194eHhdKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby90cmVlL21hc3Rlci9wcmVzZW50YXRpb25zL2RhdGEvZGlzY2hhcmdlLmNzdiksIApvciBiZXR0ZXI6IFtkb3dubG9hZCB0aGUgd2hvbGUgZ2l0aHViIGNvdXJzZSByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vYXJjaGl2ZS9tYXN0ZXIuemlwKQoKUmVhZCBhbmQgdHJhbnNmb3JtIGRhdGEKYGBge3J9ClEgPC0gcmVhZC50YWJsZSgiZGF0YS9kaXNjaGFyZ2UzOTA3Mi5jc3YiLCBza2lwPTE5LCBoZWFkZXI9VFJVRSwgc2VwPSIsIiwgZmlsbD1UUlVFKVssMToyXQpjb2xuYW1lcyhRKSA8LSBjKCJkYXRlIiwiUm95YWxfV2luZHNvcl9QYXJrIikKUSRkYXRlIDwtIGFzLkRhdGUoUSRkYXRlLCBmb3JtYXQ9IiVZLSVtLSVkIikKYGBgCgpFeGFtaW5lIGRhdGEKYGBge3J9CmhlYWQoUSkKYGBgCgpgYGB7cn0Kc3RyKFEpCmBgYAoKU2ltcGxlIHRpbWUgc2VyaWVzIHBsb3QKYGBge3J9CnBsb3QoUSwgdHlwZT0ibCIsIGNvbD0iYmx1ZSIpCmBgYAoKUHVibGljYXRpb24tcmVhZHkgZ3JhcGhpY3MKYGBge3J9CnBuZygiRGlzY2hhcmdlVmlzLnBuZyIsIHdpZHRoPTIwLCBoZWlnaHQ9MTAsIHVuaXRzPSJjbSIsIHJlcz01MDApCiNwZGYoIkRpc2NoYXJnZVZpcy5wZGYiLCB3aWR0aD0yMC8yLjUsIGhlaWdodD0xMC8yLjUpICMgdmVjdG9yIGdyYXBoCnBhcihtYXI9YygzLjUsMy41LDIuNSwwLjIpLCBtZ3A9YygyLjMsMC43LDApLCBsYXM9MSkKcGxvdChRLCB0eXBlPSJsIiwgY29sPSJibHVlIiwgbWFpbj0iTlJGQTogVGhhbWVzXG5Sb3lhbCBXaW5kc29yIFBhcmsiLAogICAgIHhsYWI9IkRhdGUiLCB5bGFiPSJEaXNjaGFyZ2UgIFttXFV7MDBCM30vc10iKQpkZXYub2ZmKCkKYGBgCgpBbm51YWwgbWF4aW1hLCBHZXJtYW4gaHlkcm9sb2dpY2FsIHllYXIgc3BsaXQgYXQgT2N0IDMxCmBgYHtyfQpRJGh5ZHllYXIgPC0gYXMubnVtZXJpYyhmb3JtYXQoUSRkYXRlKzYxLCAiJVkiKSkKYW5ubWF4IDwtIHRhcHBseShRJFJveWFsX1dpbmRzb3JfUGFyaywgUSRoeWR5ZWFyLCBtYXgsIG5hLnJtPVRSVUUpCmFubm1heCA8LSBhbm5tYXhbLTFdCmh5ZHllYXIgPC0gYXMubnVtZXJpYyhuYW1lcyhhbm5tYXgpKSAKcGxvdChoeWR5ZWFyLCBhbm5tYXgsIHR5cGU9Im8iLCBsYXM9MSkKYGBgCgojIyMgRXh0cmVtZSB2YWx1ZSBzdGF0aXN0aWNzCgpgYGB7cn0KbGlicmFyeShleHRyZW1lU3RhdCkKZGxmIDwtIGRpc3RMZml0KGFubm1heCkKcGxvdExmaXQoZGxmKQpwbG90TGZpdChkbGYsIGNkZj1UUlVFKQpkbGUgPC0gZGlzdExleHRyZW1lKGRsZj1kbGYsIFJQcz1jKDUsMTAsNTAsMTAwKSwgZ3BkPUZBTFNFKQpwbG90TGV4dHJlbWUoZGxlKQpgYGAKCkxvZ2FyaXRobWljIHBsb3Qgd2l0aCBtYW55IGZpdHRlZCBkaXN0cmlidXRpb24gZnVuY3Rpb25zCmBgYHtyfQpwbG90TGV4dHJlbWUoZGxlLCBuYmVzdD0xNiwgbG9nPVRSVUUpCmBgYAoKUmV0dXJuIHZhbHVlcyAoZGlzY2hhcmdlIGVzdGltYXRlcyBmb3IgZ2l2ZW4gcmV0dXJuIHBlcmlvZHMpCmBgYHtyfQpkbGYkcmV0dXJubGV2CmBgYApJbiByZWFsaXR5LCBwbGVhc2UgdXNlIG5vbi1zdGF0aW9uYXJ5IEVWUyEKClVuY2VydGFpbnR5IGJhbmQgZm9yIFdha2VieSBkaXN0cmlidXRpb24gZml0IGVzdGltYXRlCmBgYHtyfQpkbGVfYm9vdCA8LSBkaXN0TGV4Qm9vdChkbGUsIG49MTAsIG5iZXN0PTEpCnBsb3RMZXhCb290KGRsZV9ib290LCBkaXN0Y29sPSJncmVlbiIpCmBgYAoKCk1vcmUgZGV0YWlscyBpbiB0aGUgcGFja2FnZSB2aWduZXR0ZQpgYGB7cn0KdmlnbmV0dGUoImV4dHJlbWVTdGF0IikKYGBgCgoKIyMjIEFuaW1hdGVkIG1vdmllCgoKUmVhZCBkYXRhIGZyb20gc2V2ZXJhbCBkaXNjaGFyZ2Ugc3RhdGlvbnMKYGBge3J9CmlmKEZBTFNFKXsKUTIgPC0gcmVhZC50YWJsZSgiZGF0YS9kaXNjaGFyZ2VfX19fXy5jc3YiLCBza2lwPTE5LCBoZWFkZXI9VFJVRSwgc2VwPSIsIiwgZmlsbD1UUlVFKVssMToyXQpjb2xuYW1lcyhRMikgPC0gYygiZGF0ZSIsImR1bW15MSIpOyAgUTIkZGF0ZSA8LSBhcy5EYXRlKFEyJGRhdGUsIGZvcm1hdD0iJVktJW0tJWQiKQpRMyA8LSByZWFkLnRhYmxlKCJkYXRhL2Rpc2NoYXJnZV9fX19fLmNzdiIsIHNraXA9MTksIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBmaWxsPVRSVUUpWywxOjJdCmNvbG5hbWVzKFEzKSA8LSBjKCJkYXRlIiwiZHVtbXkyIik7ICBRMyRkYXRlIDwtIGFzLkRhdGUoUTMkZGF0ZSwgZm9ybWF0PSIlWS0lbS0lZCIpCn0KUTIgPC0gUSA7IFEyWywyXSA8LSBRMlssMl0gKyAxMDAgOyBRMiA8LSBRMlstKDE6MTAwKSxdClEzIDwtIFEgOyBRM1ssMl0gPC0gUTNbLDJdIC0gIDUwIDsgUTMgPC0gaGVhZChRMywgLTM2NSozKQpjb2xuYW1lcyhRMilbMl0gPC0gImR1bW15MSI7IGNvbG5hbWVzKFEzKVsyXSA8LSAiZHVtbXkyIgpgYGAKCk1lcmdlIGRhdGFzZXRzCmBgYHtyfQpkaXMgPC0gUmVkdWNlKGZ1bmN0aW9uKC4uLikgbWVyZ2UoLi4uLCBhbGw9VCksIGxpc3QoUSxRMixRMykpCmBgYAoKQ29kZSB0byBnZW5lcmF0ZSBvbmUgbW92aWUgc2xpY2UKYGBge3J9CmxpYnJhcnkoYmVycnlGdW5jdGlvbnMpICMgZm9yIGxpbTAsIG1vbnRoQXhpcywgdGV4dEZpZWxkLCBldGMKCnNjZW5lIDwtIGZ1bmN0aW9uKGksIHZsY25vdGU9VFJVRSwgY2V4PTEuMikKewogc2VsIDwtIDA6MTIwCiBkaXMyIDwtIGRpc1tpICsgc2VsLCBdCiBzdGF0IDwtIGMoIlJveWFsX1dpbmRzb3JfUGFyayIsICJkdW1teTEiLCAiZHVtbXkyIikKIGNvbCA8LSBjKCJyZWQiLCAiYmx1ZSIsICJvcmFuZ2UiKQogbmFtZXMoY29sKSA8LSBzdGF0CiBwbG90KGRpczIkZGF0ZSxkaXMyJFJveWFsX1dpbmRzb3JfUGFyaywgdHlwZT0ibiIsIHlsaW09bGltMCg1MDApLCAgbGFzPTEsIAogICAgICB4YXh0PSJuIiwgeWF4dD0ibiIsIGNleC5sYWI9Y2V4LCB4bGFiPSIiLCAKICAgICAgeWxhYj0iRGlzY2hhcmdlICBbbVxVezAwQjN9L3NdIiwgeGF4cz0iaSIpCiBheGlzKDIsIGNleC5heGlzPWNleCwgbGFzPTEpCiBTeXMuc2V0bG9jYWxlKCJMQ19USU1FIiwgIkMiKQogbW9udGhBeGlzKG1pZG1vbnRoPVRSVUUsIGZvcm1hdD0iJWJcbiVZIiwgY2V4PWNleCwgbWdwPWMoMywzLjUsMCkpCiBhYmxpbmUoaD0xOjYqMTAwLCB2PWRpczIkZGF0ZVtmb3JtYXQoZGlzMiRkYXRlLCIlZCIpPT0iMDEiXSwgY29sPTgpCiBmb3IocyBpbiBzdGF0KSBsaW5lcyhkaXMyJGRhdGUsIGRpczJbLHNdLCBsd2Q9NCwgY29sPWNvbFtzXSkKIHhpIDwtIHNlcVIoc2VsLGxlbj1sZW5ndGgoc3RhdCkrMilbLTFdCiB4aSA8LSBoZWFkKHhpLCAtMSkKIHRleHRGaWVsZChkaXMyJGRhdGVbeGldLCBkaWFnKGFzLm1hdHJpeChkaXMyW3hpLHN0YXRdKSksIHN0YXQsIGNleD1jZXgsIGNvbD1jb2wpCiBib3goKQogaWYodmxjbm90ZSkgbXRleHQoIlZMQzogJ2UnOiBzaW5nbGUgZnJhbWUgZm9yd2FyZFxuJ1NISUZUK0xFRlQnOiBmZXcgc2Vjb25kcyBiYWNrIiwKICAgICAgICAgICAgICAgICAgIHNpZGU9MywgbGluZT0tOSwgb3V0ZXI9VFJVRSwgYWRqPTAuOTUsIGNleD1jZXgpCn0KCnBhcihtYXI9Yyg1LDUsMC41LDAuNSksIG1ncD1jKDMsMC43LDApKQpzY2VuZSgxNTApCmBgYAoKQWN0dWFsIG1vdmllCmBgYHtyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KGFuaW1hdGlvbikKc2F2ZVZpZGVvKHtwYXIobWFyPWMoNiw4LDEsMSksIG1ncD1jKDUuNSwwLjcsMCkpCiBkdW1teSA8LSBwYnNhcHBseShzZXEoMCwgYnk9MywgbGVuPTUwKSwgc2NlbmUsIGNleD0yKTsgcm0oZHVtbXkpCn0sIHZpZGVvLm5hbWU9IlFtb3ZpZS5tcDQiLCBpbnRlcnZhbD0wLjEsIGZmbXBlZz0iL3Vzci9iaW4vZmZtcGVnIiwgCmFuaS53aWR0aD03KjIwMCwgYW5pLmhlaWdodD01KjIwMCkKYGBgCgpbT3BlbiB2aWRlbyBpbiBkZWZhdWx0IG1wNHBsYXllcl0oUW1vdmllLm1wNCkKClwKW3RvcF0oI3RvcCkKCiMgSHlkbW9kCkh5ZHJvbG9naWNhbCBtb2RlbGxpbmcgd2l0aCBgYWlyR1JgICAgCioqS2F0aWUgU21pdGgqKgoKXApbdG9wXSgjdG9wKQoKIyBFREEKRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyBpbmNsdWRpbmcgZmxvdyBkdXJhdGlvbiBjdXJ2ZSBhbmQgdHJlbmQgYW5hbHlzaXMgb24gdGltZS1zZXJpZXMgICAKKipTaGF1biBIYXJyaWdhbioqCgpcClt0b3BdKCN0b3ApCgoKIyBEaXNjdXNzaW9uCgpQbGVhc2UgZ2l2ZSB1cyBmZWVkYmFjayBhdAo8Zm9udCBzaXplPSI2Ij5bYml0Lmx5L2ZlZWRiYWNrUl0oaHR0cHM6Ly9iaXQubHkvZmVlZGJhY2tSKTwvZm9udD4gCgpGb3IgZGlzY3Vzc2lvbnMsIHBsZWFzZSB1c2UgdGhlIApbSHlkcm9sb2d5IGluIFIgRmFjZWJvb2sgZ3JvdXBdKGh0dHBzOi8vd3d3LmZhY2Vib29rLmNvbS9ncm91cHMvMTEzMDIxNDc3NzEyMzkwOS8pLiAgCgo=